package com.ymt.bpm.service.platform.attach;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.*;
import com.ymt.bpm.appboot.env.Caches;
import com.ymt.bpm.dao.MDao;
import com.ymt.bpm.dao.Mapper;
import com.ymt.bpm.engine.EngineConst;
import com.ymt.bpm.model.PAttachment;
import com.ymt.bpm.model.PAttachmentVersion;
import com.ymt.bpm.model.PAttachmentVersionKey;
import com.ymt.bpm.service.platform.profile.TenantService;
import com.ymt.bpm.util.AES;
import com.ymt.bpm.util.Const;
import com.ymt.bpm.util.DateUtil;
import com.ymt.bpm.util.DhxUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.*;

/**
 * @Author Johnny
 */
@Service
public class AttachmentService {

    /**
     * Logger
     */
    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private Environment env;

    @Autowired
    private TenantService ts;

    @Autowired
    private MDao dao;

    private OSSClient client;
    private String schema;
    private String endpoint;
    private String bucketName;
    private String accessId;
    private String accessSecret;
    private String prefix;
    private long expire;
    private long sizeLimit;
    private String previewTypes;

    private final int ACTION_NEW = 1;
    private final int ACTION_UPDATE = 2;

    //@Cacheable(cacheNames= Caches.TENANT, key="'ATCFG_'+#tenantId")
    public JSONObject initConf(String tenantId) {

        init();

        JSONObject rtn = new JSONObject();

        rtn.put("prefix", prefix);
        if (tenantId==null) {
            rtn.put("tenantPath", AES.encrypt("0"));
        } else {
            rtn.put("tenantPath", AES.encrypt(tenantId));
        }

        rtn.put("limit", sizeLimit);

        if (tenantId==null) {
            tenantId = "0";
        }
        JSONObject tconf = ts.getTenantConf(tenantId);

        rtn.put("attachment-enabled", tconf.getBoolean("attachment-enabled"));
        if (tconf.containsKey("attachment-size-limit")) {
            rtn.put("limit", tconf.getString("attachment-size-limit"));
        }

        return rtn;
    }

    public JSONObject signatureUp(int timezoneOffset) throws UnsupportedEncodingException {

        if (log.isDebugEnabled()) {
            log.debug("timezoneOffset="+timezoneOffset);
        }

        init();

        Date expiration = getExpireDate(timezoneOffset);
        PolicyConditions policyConds = new PolicyConditions();
        policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 5242880);
        //policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

        String postPolicy = client.generatePostPolicy(expiration, policyConds);
        byte[] binaryData = postPolicy.getBytes(Const.ENCODING);
        String encodedPolicy = BinaryUtil.toBase64String(binaryData);
        String postSignature = client.calculatePostSignature(postPolicy);

        JSONObject rtn = new JSONObject();
        rtn.put("accessid", accessId);
        rtn.put("policy", encodedPolicy);
        rtn.put("signature", postSignature);
        //rtn.put("expire", formatISO8601Date(expiration));
        rtn.put("host", schema + bucketName +"."+ endpoint);
        rtn.put("expire", String.valueOf(expiration.getTime() / 1000));

        return rtn;
    }

    public JSONObject signatureDown(String key, int timezoneOffset) {
        init();

        if (log.isDebugEnabled()) {
            log.debug("key="+key);
        }
        String url = getSignedUrl(key, getExpireDate(timezoneOffset));

        JSONObject rtn = new JSONObject();
        rtn.put("url", url);
        return rtn;
    }

    public JSONObject signaturePreview(String attachmentId, String versionId, int timezoneOffset) {
        init();

        if (log.isDebugEnabled()) {
            log.debug("attachmentId="+attachmentId);
            log.debug("versionId="+versionId);
        }

        JSONObject rtn = new JSONObject();

        PAttachment attachment = dao.selectOne(Mapper.PAttachmentMapper, "selectByPrimaryKey", Long.parseLong(attachmentId));
        String url = null;
        String type = null;
        String name = attachment.getName();

        if (versionId==null) {
            url = getSignedPreviewUrl(attachment.getPath(), attachment.getType(), getExpireDate(timezoneOffset));
            type = attachment.getType();
        } else {
            PAttachmentVersionKey key = new PAttachmentVersionKey();
            key.setAttachmentId(Long.parseLong(attachmentId));
            key.setVersionId(Integer.parseInt(versionId));
            PAttachmentVersion version = dao.selectOne(Mapper.PAttachmentVersionMapper, "selectByPrimaryKey", key);
            url = getSignedPreviewUrl(version.getPath(), version.getType(), getExpireDate(timezoneOffset));
            type = version.getType();
        }

        if (type!=null && type.indexOf("image")>=0) {
            rtn.put("showType", "iframe");
        } else if (type!=null && type.indexOf("pdf")>=0) {
            rtn.put("showType", "iframe");
        } else {
            //text, ms office, xml etc.
            rtn.put("showType", "url");
        }
        rtn.put("name", name);
        rtn.put("type", type);
        rtn.put("previewUrl", url);
        return rtn;
    }

    /*public JSONArray listFiles(String prefix, int timezoneOffset) {
        OSSClient client = new OSSClient(endpoint, accessId, accessSecret);
        try {
            //list files
            ListObjectsRequest req = new ListObjectsRequest(bucketName, prefix, null, null, 100);
            ObjectListing listing = client.listObjects(req);
            System.out.println(listing.getObjectSummaries().get(0).getKey());
            System.out.println(listing.getObjectSummaries().get(1).getKey());
            ObjectMetadata meta = client.getObjectMetadata(bucketName, listing.getObjectSummaries().get(1).getKey());
            System.out.println(meta.getUserMetadata());
        } finally {
            client.shutdown();
        }
    }*/

    @Transactional(EngineConst.TRANSACTION_MANAGER)
    public JSONObject saveToDbFiles(String tenantId, String loginName, String displayName,
                                   String taskInfoStr, String filesStr, int timezoneOffset) {
        if (log.isDebugEnabled()) {
            log.debug("saveToDbFiles tenantId="+tenantId);
            log.debug("saveToDbFiles taskInfoStr="+taskInfoStr);
            log.debug("saveToDbFiles filesStr="+filesStr);
            log.debug("saveToDbFiles timezoneOffset="+timezoneOffset);
        }

        init();

        JSONObject taskInfo = JSONObject.parseObject(taskInfoStr);
        String activityId = taskInfo.getString("activityId");
        String activityName = taskInfo.getString("activityName");
        String piId = taskInfo.getString("piId");
        String tiId = taskInfo.getString("tiId");

        JSONArray filesArr = JSONArray.parseArray(filesStr);

        JSONObject rtn = new JSONObject();
        int rs = 0;
        //Date expireDate = getExpireDate(timezoneOffset);
        Date now = DateUtil.getGMTDate();
        //Map<String, Object> paramMap = new HashMap<String, Object>();
        //paramMap.put(Const.TENANT_ID, tenantId);
        //paramMap.put("piId", piId);
        if (filesArr!=null && filesArr.size()>0) {
            List<PAttachment> attachToUpdate = new ArrayList<PAttachment>();
            List<PAttachmentVersion> versionsToDb = new ArrayList<PAttachmentVersion>();
            for (int i=0; i<filesArr.size(); i++) {
                JSONObject fileInfo = filesArr.getJSONObject(i);
                String key = fileInfo.getString("key");
                String type = fileInfo.getString("type");
                String filename = fileInfo.getString("filename");
                long size = fileInfo.getLong("size");
                if (size > sizeLimit) {
                    continue;
                }
                int action = fileInfo.getInteger("action");
                if (action==ACTION_NEW) {
                    PAttachment attachment = new PAttachment();
                    PAttachmentVersion version = new PAttachmentVersion();
                    if (tenantId!=null && StringUtils.isNumeric(tenantId)) {
                        attachment.setTenantId(Integer.parseInt(tenantId));
                        version.setTenantId(Integer.parseInt(tenantId));
                    }
                    //main
                    attachment.setPiId(piId);
                    attachment.setTiId(tiId);
                    attachment.setActivityId(activityId);
                    attachment.setActivityName(activityName);
                    attachment.setName(filename);
                    attachment.setSize(size);
                    attachment.setPath(key);
                    attachment.setType(type);
                    attachment.setCreateTime(now);
                    attachment.setCreator(loginName);
                    attachment.setCreatorName(displayName);
                    attachment.setUpdater(loginName);
                    attachment.setUpdaterName(displayName);
                    attachment.setUpdateTime(now);
                    attachment.setIsValid("1");
                    //save to db to get attachmentId
                    rs += dao.insert(Mapper.PAttachmentMapper, "insertSelective", attachment);

                    //version
                    version.setAttachmentId(attachment.getAttachmentId());
                    version.setVersionId(1);
                    version.setActivityId(activityId);
                    version.setActivityName(activityName);
                    version.setCreateTime(now);
                    version.setCreator(loginName);
                    version.setCreatorName(displayName);
                    version.setPath(key);
                    version.setTiId(tiId);
                    version.setSize(size);
                    version.setType(type);
                    version.setIsValid("1");
                    versionsToDb.add(version);
                } else if (action==ACTION_UPDATE) {
                    //paramMap.put("name", filename.toUpperCase());
                    //paramMap.put("attachmentId", attachmentId);
                    long attachmentId = fileInfo.getLong("attachmentId");
                    PAttachment attachment = dao.selectOne(Mapper.PAttachmentMapper, "selectByPrimaryKey", attachmentId);
                    attachment.setPath(key);
                    attachment.setSize(size);
                    attachment.setUpdateTime(now);
                    attachment.setUpdater(loginName);
                    attachment.setUpdaterName(displayName);
                    attachToUpdate.add(attachment);
                    int maxVersionId = dao.selectOne(Mapper.PAttachmentVersionMapper, "selectMaxVersion", attachmentId);
                    //version
                    PAttachmentVersion version = new PAttachmentVersion();
                    if (tenantId!=null && StringUtils.isNumeric(tenantId)) {
                        version.setTenantId(Integer.parseInt(tenantId));
                    }
                    version.setAttachmentId(attachment.getAttachmentId());
                    version.setVersionId(maxVersionId + 1);
                    version.setActivityId(activityId);
                    version.setActivityName(activityName);
                    version.setCreateTime(now);
                    version.setCreator(loginName);
                    version.setCreatorName(displayName);
                    version.setPath(key);
                    version.setTiId(tiId);
                    version.setSize(size);
                    version.setType(type);
                    version.setIsValid("1");
                    versionsToDb.add(version);
                }
            }
            rs += dao.batchUpdate(Mapper.PAttachmentMapper, "updateByPrimaryKeySelective", attachToUpdate);
            rs += dao.batchInsert(Mapper.PAttachmentVersionMapper, "insertSelective", versionsToDb);
        }
        if (rs>0) {
            return listDbFiles(tenantId, piId, tiId, timezoneOffset);
        }
        return rtn;
    }

    @Transactional(EngineConst.TRANSACTION_MANAGER)
    public JSONObject delDbFile(String tenantId, String piId, String attachmentId, String versionId) {
        if (log.isDebugEnabled()) {
            log.debug("delDbFile tenantId="+tenantId);
            log.debug("delDbFile piId="+piId);
            log.debug("delDbFile attachmentId="+attachmentId);
            log.debug("delDbFile versionId="+versionId);
        }
        JSONObject rtn = new JSONObject();
        int rs = 0;
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put(Const.TENANT_ID, tenantId);
        paramMap.put("piId", piId);
        paramMap.put("attachmentId", attachmentId);
        if (versionId!=null && versionId.length()>0) {
            paramMap.put("versionId", versionId);
            PAttachmentVersion version = dao.selectOne(Mapper.PAttachmentVersionMapper, "selectByPrimaryKeyMap", paramMap);
            deleteOSSByVersion(version);
            rs += dao.delete(Mapper.PAttachmentVersionMapper, "deleteByPrimaryKey", paramMap);
            int countVer = dao.selectOne(Mapper.PAttachmentVersionMapper, "countVersion", paramMap);
            if (countVer==0) {
                rs += dao.delete(Mapper.PAttachmentMapper, "deleteByPrimaryKey", paramMap);
            } else {
                //update to latest version path
                rs += dao.update(Mapper.PAttachmentMapper, "updatePathToLastVersion", paramMap);
            }
        } else {
            List<PAttachmentVersion> versions = dao.selectList(Mapper.PAttachmentVersionMapper, "queryByMain", paramMap);
            if (versions!=null && versions.size()>0) {
                PAttachmentVersion[] array = versions.toArray(new PAttachmentVersion[versions.size()]);
                deleteOSSByVersion(array);
            }
            rs += dao.delete(Mapper.PAttachmentVersionMapper, "deleteByAttachmentId", paramMap);
            rs += dao.delete(Mapper.PAttachmentMapper, "deleteByPrimaryKey", paramMap);
        }
        rtn.put(Const.AJAX_RESULT, rs);
        return rtn;
    }

    public JSONObject listDbFiles(String tenantId, String piId, String tiId, int timezoneOffset) {

        if (log.isDebugEnabled()) {
            log.debug("listDbFiles tenantId="+tenantId);
            log.debug("listDbFiles piId="+piId);
            log.debug("listDbFiles tiId="+tiId);
            log.debug("listDbFiles timezoneOffset="+timezoneOffset);
        }

        init();

        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put(Const.TENANT_ID, tenantId);
        paramMap.put("piId", piId);
        paramMap.put("tiId", tiId);
        List<PAttachment> attachmentsList = dao.selectList(Mapper.PAttachmentMapper, "query", paramMap);
        List<PAttachmentVersion> versionsList = dao.selectList(Mapper.PAttachmentVersionMapper, "queryByMain", paramMap);
        Date expireDate = getExpireDate(timezoneOffset);
        JSONObject rtn = new JSONObject();
        //put attachments
        JSONArray attachments = new JSONArray();
        if (attachmentsList!=null && attachmentsList.size()>0) {
            for (PAttachment attachment : attachmentsList) {
                JSONObject attach = new JSONObject();//DhxUtil.toFastJsonWithDate(attachment, Const.LANG_ZH, timezoneOffset);
                attach.put("attachmentId", attachment.getAttachmentId());
                attach.put("name", attachment.getName());
                attach.put("size", attachment.getSize());
                attach.put("tiId", attachment.getTiId());
                attach.put("creatorName", attachment.getCreatorName());
                attach.put("updaterName", attachment.getUpdaterName());
                attach.put("createTime", DateUtil.formatDate(attachment.getCreateTime(), DateUtil.DATEFORMATSS_ZH, timezoneOffset));
                attach.put("activityId", attachment.getActivityId());
                attach.put("activityName", attachment.getActivityName());
                attach.put("updateTime", DateUtil.formatDate(attachment.getUpdateTime(), DateUtil.DATEFORMATSS_ZH, timezoneOffset));
                attach.put("path", attachment.getPath());
                if (canPreview(attachment.getType())) {
                    attach.put("canPreview", AES.encrypt(String.valueOf(attachment.getAttachmentId())));
                }
                attach.put("url", getSignedUrl(attachment.getPath(), expireDate));
                attachments.add(attach);
            }
            rtn.put("attachments", attachments);
        }
        //put versions
        JSONArray versions = new JSONArray();
        if (versionsList!=null && versionsList.size()>0) {
            for (PAttachmentVersion version : versionsList) {
                JSONObject attach = DhxUtil.toFastJsonWithDate(version, Const.LANG_ZH, timezoneOffset);
                attach.put("attachmentId", version.getAttachmentId());
                attach.put("versionId", version.getVersionId());
                attach.put("size", version.getSize());
                attach.put("tiId", version.getTiId());
                attach.put("activityName", version.getActivityName());
                attach.put("creator", version.getCreator());
                attach.put("creatorName", version.getCreatorName());
                attach.put("createTime", DateUtil.formatDate(version.getCreateTime(), DateUtil.DATEFORMATSS_ZH, timezoneOffset));
                attach.put("path", version.getPath());
                if (canPreview(version.getType())) {
                    attach.put("canPreview", AES.encrypt(String.valueOf(version.getVersionId())));
                }
                //attach.put("canPreview", version.getType());
                attach.put("url", getSignedUrl(version.getPath(), expireDate));
                versions.add(attach);
            }
            rtn.put("versions", versions);
        }
        return rtn;
    }

    private String getSignedUrl(String key, Date expireDate) {
        URL url = client.generatePresignedUrl(bucketName, key, expireDate);
        return url.toString();
    }

    private String getSignedPreviewUrl(String key, String type, Date expireDate) {
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, key);
        request.setExpiration(expireDate);
        ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides();
        responseHeaders.setContentDisposition("inline");
        request.setResponseHeaders(responseHeaders);
        URL url = client.generatePresignedUrl(request);
        if (log.isDebugEnabled()) {
            log.debug("getSignedPreviewUrl="+url);
        }
        return url.toString();
    }

    private Date getExpireDate(int timezoneOffset) {
        long expireEndTime = System.currentTimeMillis() + expire * 1000;
        Date expiration = new Date(expireEndTime);
        return expiration;
    }

    private void deleteOSSByVersion(PAttachmentVersion... versions) {
        if (versions==null || versions.length==0) {
            return;
        }

        List<String> keys = new ArrayList<String>();
        for (PAttachmentVersion version : versions) {
            keys.add(version.getPath());
        }
        if (log.isDebugEnabled()) {
            log.debug("deleting OSS objects:"+keys);
        }

        OSSClient client1 = new OSSClient(schema + endpoint, accessId, accessSecret);

        try {
            DeleteObjectsResult deleteObjectsResult = client1.deleteObjects(
                    new DeleteObjectsRequest(bucketName).withKeys(keys));
            List<String> deletedObjects = deleteObjectsResult.getDeletedObjects();
            if (log.isDebugEnabled()) {
                for (String object : deletedObjects) {
                    log.debug("deleted OSS object:" + object);
                }
            }
        } finally {
            client1.shutdown();
        }
    }

    private boolean canPreview(String type) {
        if (type.indexOf("image/")>=0) {    //all images
            return true;
        }
        if (previewTypes!=null) {
            List<String> pts = Arrays.asList(previewTypes.split("\\|"));
            return pts.indexOf(type)>=0;
        }
        return false;
    }

    private void init() {
        if (client==null) {
            schema = env.getProperty("oss.attachment.schema");
            endpoint = env.getProperty("oss.attachment.endpoint");
            bucketName = env.getProperty("oss.attachment.bucket-name");
            accessId = env.getProperty("oss.attachment.access-key-id");
            accessSecret = env.getProperty("oss.attachment.access-key-secret");
            prefix = env.getProperty("oss.attachment.prefix");
            expire = Long.parseLong(env.getProperty("oss.attachment.expire", "36000"));
            sizeLimit = Long.parseLong(env.getProperty("oss.attachment.size-limit", "size-limit"));
            previewTypes = env.getProperty("oss.attachment.preview-types");
            if (log.isDebugEnabled()) {
                log.debug("previewTypes="+previewTypes);
            }
            client = new OSSClient(schema + endpoint, accessId, accessSecret);
        }
    }

    public static void main(String[] args) {
        OSSClient client = new OSSClient("https://oss-cn-beijing.aliyuncs.com", "LTAIXgGWnWlBVXsR", "E1X87LK38f9cDmbh06Wezn4OiizWMA");
        //get signed download url
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest("test-file-bpmyun-top", "1.txt");
        long signExpire = 36000;
        long expireEndTime = System.currentTimeMillis() + signExpire * 1000;
        Date expiration = new Date(expireEndTime);
        request.setExpiration(expiration);
        ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides();
//        responseHeaders.setContentDisposition("attachment;filename=xxx.txt");
        responseHeaders.setContentDisposition("inline");
        request.setResponseHeaders(responseHeaders);
        URL url = client.generatePresignedUrl(request);
        System.out.println(url);

        //list files
        /*ListObjectsRequest req = new ListObjectsRequest("test-file-bpmyun-top", "test/", null, null, 100);
        ObjectListing listing = client.listObjects(req);
        System.out.println(listing.getObjectSummaries().get(0).getKey());
        System.out.println(listing.getObjectSummaries().get(1).getKey());
        ObjectMetadata meta = client.getObjectMetadata("test-file-bpmyun-top", listing.getObjectSummaries().get(1).getKey());
        System.out.println(meta.getUserMetadata());
        client.shutdown();*/
    }

}
